/**
 * @fileoverview
 * This script defines a widget for finding Drosophila melanogaster genes via FlyBase.
 * @author <a href="http://purl.org/net/aliman">Alistair Miles</a>
 * @version $Revision:539 $ on $Date: 2008-08-13 16:09:30 +0100 (Wed, 13 Aug 2008) $ by $Author: aliman $
 * @requires YAHOO.log
 * @requires YAHOO.lang.JSON
 * @requires YAHOO.util.Dom
 * @requires YAHOO.util.Connect
 * @requires YAHOO.util.CustomEvent
 * @requires flyui.util
 * @requires flyui.flybase
 * TODO license terms
 */
 
 
// create a namespace if not already defined
flyui.namespace("flyui.genefinder");


/** 
 * Create a new widget instance.
 * @class
 * A widget for finding and displaying D. melanogaster genes from FlyBase.
 * @constructor
 * @param {flyui.flybase.Service} service the service to use to retrieve data from
 * @param {Object} renderer the renderer to use with this widget instance (see also flyui.genefinder.DefaultRenderer)
 */
flyui.genefinder.Widget = function( service, renderer ) {
	
	var that = this;
	
	/** @private */
	this._controller = null;

	/**
	 * @private
	 * @type flyui.mvcutils.GenericModel
	 */	
	this._model = null;
	
	/**
	 * @private
	 */
	this._renderer = renderer;
	
	/**
	 * @private
	 */
	this._geneSelectedEvent = new YAHOO.util.CustomEvent("GENESELECTED", this);
	
	/**
	 * TODO doc me
	 * @private
	 */
	this._init = function() {
		// create a model
		this._model = new flyui.mvcutils.GenericModel(flyui.genefinder.modelDefinition);
		
		// instantiate the controller
		this._controller = new flyui.genefinder.Controller(this._model, service, this);
		
		// connect the renderer to the model
		this._renderer.connect(this._model);
		
		this._renderer.setResultClickHandler(this.onResultClicked);
	};
	
//	// DOM event handlers
//	this.makeOnResultClickedHandler = function(index) {
//		return function(event) {
//			log("received click event, call the controller to set selection: "+index);	
//			that._controller.setSelectionIndex(index);
//		};
//	}
	
	// DOM event handlers
	this.onResultClicked = function( event, index ) {
		log("received click event, call the controller to set selection: "+index);	
		that._controller.setSelectionIndex(index);
	}

	// do initialisation
	this._init();
		
	/**
	 * Subscribe to a custom event.
	 * @param type the type of the event
	 * @param listener the callback function
	 */
	this.subscribe = function(type, listener, obj) {
		if (type == "GENESELECTED") {
			this._geneSelectedEvent.subscribe(listener, obj);
		}
	};
	
}


/**
 * TODO doc me
 */
flyui.genefinder.Widget.prototype.findGenesByAnyName = function( anyName, caseSensitive ) {
	// pass through to controller
	this._controller.findGenesByAnyName(anyName, caseSensitive);
}


/** 
 * Create a new controller instance.
 * @class
 * Controller class for the flyui.genefinder widget.
 * @constructor
 * @param {flyui.mvcutils.GenericModel} model the model to use to store data
 * @param {flyui.flybase.Service} service the service to use to retrieve data from
 * @param {flyui.genefinder.Widget} widget the widget for which this is the controller
 */
flyui.genefinder.Controller = function( model, service, widget ) {
	
	var that = this;
	
	/**
	 * @private
	 */
	this._model = model;

	/**
	 * @private
	 */
	this._service = service;

	/**
	 * @private
	 */
	this._parent = widget;
	
	
	/**
	 * TODO doc me
	 * @private
	 */
	this._findGenesByAnyNameSuccess = function( genes ) {

		log("request success");
		
		// set the results
		that._model.set("RESULTS", genes);
		
		// set model state
		that._model.set("STATE", "READY");
		
		// auto-select if only one result
		if (genes.length == 1) {
			that.setSelectionIndex(0);
		}
		
	}
	
	
	/**
	 * TODO doc me
	 * @private
	 */
	this._findGenesByAnyNameFailure = function( response ) {
		
		log("request failed, status "+response.status+" "+response.statusText, "error");
		
		// set the error message
		var msg = "There was an error retrieving data from FlyBase, see the logs for more info. The server may be busy or down, please try again later. If this message persists, please contact the Image Bioinformatics Research Group at bioimage@mail.ontonet.org.";		
		that._model.set("ERRORMESSAGE", msg);

		// set the state
		that._model.set("STATE", "SERVERERROR");		
	}
	
}


/**
 * Find D. melanogaster genes by any name (including symbols, flybase IDs, synonyms).
 * @param {String} anyName any name, symbol or synonym
 * @param {String} caseSensitive
 */
flyui.genefinder.Controller.prototype.findGenesByAnyName = function( anyName, caseSensitive ) {
	log("findGenesByAnyName: "+anyName);

	log("success callback: "+this._findGenesByAnyNameSuccess, "debug");
	
	// pass through to private method
	this._findGenesByAnyName(anyName, this._findGenesByAnyNameSuccess, this._findGenesByAnyNameFailure, caseSensitive);
}


/**
 * Implementation of findGenesByAnyName, allowing insertion of custom callbacks for testing.
 * @param {String} anyName any name, symbol or synonym
 * @param {Function} success callback
 * @param {Function} failure callback
 * @param {boolean} caseSensitive
 * @private
 */
flyui.genefinder.Controller.prototype._findGenesByAnyName = function( anyName, success, failure, caseSensitive ) {
	log("_findGenesByAnyName: "+anyName, "debug");
	
	log("set state pending", "debug");
	this._model.set("STATE", "PENDING");
	
	log("set model property query", "debug");
	this._model.set("QUERY", anyName);
	
	log("kick off request", "debug");
	if (caseSensitive) {
		this._service.findGenesByAnyName(anyName, success, failure);
	} else {
		this._service.findGenesByAnyNameCaseInsensitive(anyName, success, failure);
	}
}


/**
 * TODO doc me
 */
flyui.genefinder.Controller.prototype.setSelectionIndex = function( index ) {
	log("setSelectionIndex, index: "+index, "debug");
	var results = this._model.get("RESULTS");
	if (results != null) {
		if (index >= 0 && index < results.length) {
			this._model.set("SELECTIONINDEX", index);
//			log("widget: "+this._parent, "debug");
			var event = this._parent._geneSelectedEvent;
//			log("event: "+event, "debug");
			event.fire(results[index]);
		}
		else {
			throw {name:"flyui.genefinder.SelectionOutOfBounds", message:"index "+index+" cannot apply to results length "+results.length};
		}
	}
}


/** 
 * Create a new renderer instance.
 * @class
 * A default renderer for the Widget.
 * @constructor
 * @param {Element} renderPane the element to which the widget is rendered
 */
flyui.genefinder.DefaultRenderer = function( renderPane ) {

	
	var that = this;
	
	
	/**
	 * @private
	 */
	this._containerPane = renderPane;
	
	
	/**
	 * @private
	 */
	this._pendingPane = null;


	/**
	 * @private
	 */
	this._messagePane = null;
	 
	
	/**
	 * @private
	 */
	this._resultsSummaryPane = null;
	

	/**
	 * @private
	 */
	this._resultsPane = null;
	
	/**
	 * @private
	 */
	this._resultsSummaryQueryPane = null;
	
	/**
	 * @private
	 */
	this._resultsSummaryCountPane = null;
	
	
	/**
	 * @private
	 */
	this._onResultClicked = null;


	/**
	 * TODO doc me
	 */
	this.setResultClickHandler = function( handler ) {
		this._onResultClicked = handler;
	}

	/**
	 * TODO doc me
	 * @private
	 */
	this._onStateChanged = function( oldVal, newVal ) {
		
		if ( newVal == "PENDING" ) {
			flyui.mvcutils.show(that._pendingPane);
			flyui.mvcutils.hide(that._messagePane);
			flyui.mvcutils.hide(that._resultsSummaryPane);
			flyui.mvcutils.hide(that._resultsPane);
		}
		else if ( newVal == "READY" ) {
			flyui.mvcutils.hide(that._pendingPane);
			flyui.mvcutils.hide(that._messagePane);
			flyui.mvcutils.show(that._resultsSummaryPane);
			flyui.mvcutils.show(that._resultsPane);			
		} 
		else if ( newVal == "SERVERERROR" || newVal == "UNEXPECTEDERROR" ) {
			flyui.mvcutils.hide(that._pendingPane);
			flyui.mvcutils.show(that._messagePane);
			flyui.mvcutils.hide(that._resultsSummaryPane);
			flyui.mvcutils.hide(that._resultsPane);			
		} 
		else {
			// this should never happen
			throw {name:"flyui.genefinder.UnexpectedStateError", message:"Invalid state: "+newVal};
		}
		
		
	};


	/**
	 * TODO doc me
	 * @private
	 */
	this._onQueryChanged = function( oldVal, newVal ) {
		that._resultsSummaryQueryPane.innerHTML = newVal;
		that._resultsSummaryCountPane.innerHTML = "this should never be displayed";
	};


	/**
	 * TODO doc me
	 * @private
	 */
	this._onResultsChanged = function( oldVal, genes ) {

		// set the count in the summary
		that._resultsSummaryCountPane.innerHTML = genes.length;
		
		if (genes.length > 0) {
			
			// empty the results pane
			that._resultsPane.innerHTML = "";	

			// set the content
			var table = that.genesToTableNode(genes);
			log("table content:"+table.innerHTML, "debug");
			that._resultsPane.appendChild(table);	

		}
		else {
			that._resultsPane.innerHTML = "";
		}
	};
	

	/**
	 * TODO doc me
	 * @private
	 */
	this._onSelectionIndexChanged = function( oldIndex, newIndex ) {
		log("onSelectionIndexChanged, old:"+oldIndex+", new:"+newIndex, "debug");
		
//		// get the table
//		var table = that._resultsPane.childNodes[0];
//		
//		log("results table: "+table.innerHTML, "debug");
		
		// get the rows
		var rows = that._resultsPane.getElementsByTagName("tr");
		
		if (oldIndex != null) {
			// remove class
			YAHOO.util.Dom.removeClass(rows[oldIndex+1], "selected");
		}
		
		if (newIndex != null) {
			// add class
//			log("adding selected class to row: "+rows[newIndex+1], "debug");
			YAHOO.util.Dom.addClass(rows[newIndex+1], "selected");
		}
	};
	
	
	/**
	 * TODO doc me
	 * @private
	*/
	this._onErrorMessageChanged = function( oldVal, newVal ) {
		that._messagePane.innerHTML = newVal;
	};
	
	
	/**
	 * TODO doc me
	 * @private
	 */
	this._init = function() {
		
		// setup pending pane
		this._pendingPane = document.createElement("div");
		this._pendingPane.innerHTML = "<p>Pending...</p>";
		this._containerPane.appendChild(this._pendingPane);
		flyui.mvcutils.hide(this._pendingPane);

		// message pane
		this._messagePane = document.createElement("div");
		this._messagePane.innerHTML = "No genes selected";
		this._containerPane.appendChild(this._messagePane);
			
		// setup results summary pane
		this._resultsSummaryPane = document.createElement("div");
		this._containerPane.appendChild(this._resultsSummaryPane);
		flyui.mvcutils.hide(this._resultsSummaryPane);
		
		// set up results summary content
		this._resultsSummaryCountPane = document.createElement("span");
		this._resultsSummaryCountPane.innerHTML = "this should never be displayed";
		this._resultsSummaryQueryPane = document.createElement("span");
		this._resultsSummaryQueryPane.innerHTML = "this should never be displayed";
		this._resultsSummaryPane.appendChild(document.createTextNode("Found "));
		this._resultsSummaryPane.appendChild(this._resultsSummaryCountPane);
		this._resultsSummaryPane.appendChild(document.createTextNode(" matches for query \""));
		this._resultsSummaryPane.appendChild(this._resultsSummaryQueryPane);
		this._resultsSummaryPane.appendChild(document.createTextNode("\""));
		
		// setup results pane
		this._resultsPane = document.createElement("div");
		this._containerPane.appendChild(this._resultsPane);
		flyui.mvcutils.hide(this._resultsPane);
		
	};
	
	
	// do initialisation
	this._init();
	
	
}


/**
 * TODO doc me
 */
flyui.genefinder.DefaultRenderer.prototype.connect = function( model ) {
		
	model.addListener("STATE", this._onStateChanged);
	model.addListener("QUERY", this._onQueryChanged);
	model.addListener("RESULTS", this._onResultsChanged);
	model.addListener("ERRORMESSAGE", this._onErrorMessageChanged);
	model.addListener("SELECTIONINDEX", this._onSelectionIndexChanged);

}

/**
 * Helper function for building DOM elements containing lists of elements
 * @param {string} outerTag outer tag (e.g. "TR") 
 * @param {string} innerTag inner tag (e.g. "TD") 
 * @param {string*} list of inner HTML values to populate element
 * @return a new DOM element constructed from supplied values
 * @type HTMLElement 
 */
flyui.genefinder.DefaultRenderer.renderDomElementFromList = function(outerTag, innerTag, list) {

	var outerElem = document.createElement(outerTag);

	// can we defer this to caller?  tbody.appendChild(headerRow);
	
	log("creating outer "+outerTag, "debug");
	for (var i=0; i<list.length; i++) {
		var innerElem = document.createElement(innerTag);
		innerElem.innerHTML = list[i];
		outerElem.appendChild(innerElem);
	}	

	return outerElem;	
}


/**
 * TODO doc me
 */
flyui.genefinder.DefaultRenderer.prototype.genesToTableNode = function( genes ) {
	
	var table = document.createElement("table");
	var tbody = document.createElement("tbody");
	table.appendChild(tbody);

	var headers = ["Symbol", "Name", "Annotation ID", "Link to FlyBase"];
	var headerRow = flyui.genefinder.DefaultRenderer.renderDomElementFromList("tr", "th", headers); 
	tbody.appendChild(headerRow);
	
	for (var i=0; i<genes.length; i++) {
		log("creating result row "+i, "debug");
		var cellContents = flyui.genefinder.DefaultRenderer.geneToTableCellContents(genes[i]);
		var row = flyui.genefinder.DefaultRenderer.renderDomElementFromList("tr", "td", cellContents);
		tbody.appendChild(row);
		YAHOO.util.Event.addListener(row, "click", this._onResultClicked, i);
	}
	
	return table;
}


flyui.genefinder.DefaultRenderer.geneToTableCellContents = function( gene ) {
	return [
		gene.symbols.join(" / "),
		gene.fullNames.join(" / "),
		gene.annotationSymbols.join(" / "),
		"<a href=\"http://flybase.org/reports/"+gene.flybaseID+".html\">"+gene.flybaseID+"</a>"
	];
}


/** 
 * Definition of genefinder widget model.
 */
flyui.genefinder.modelDefinition = {

	propertyNames : [ "STATE", "RESULTS", "ERRORMESSAGE", "QUERY", "SELECTIONINDEX" ],
	
	controlledValues : {
		"STATE" : [ "READY", "PENDING", "SERVERERROR", "UNEXPECTEDERROR" ]
	},
	
	initialize : function( data ) {
		data["STATE"] = "READY";
		data["RESULTS"] = null;
		data["ERRORMESSAGE"] = null;
		data["QUERY"] = null;
		data["SELECTIONINDEX"] = null;
	}
}